package app

import (
	"fmt"
	"io"
	"log/slog"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"gitee.com/jiebaiyou/demo-grpc/shared/utils"
)

var (
	rfh  *rotatingFileHandler // 日志文件轮换操作
	once sync.Once            // 确保日志文件轮换的协程只会被执行一次
)

func InitLogger() error {
	slog.Info("初始化日志记录器...")

	// 设置日志格式(json 或 text)
	var logger *slog.Logger

	// 日志处理器选项
	opts := slog.HandlerOptions{
		AddSource:   Config.LOG.ADD_SOURCE,         // 是否显示源代码位置
		Level:       getLogLevel(Config.LOG.LEVEL), // 日志级别
		ReplaceAttr: getTimeFormatter(),            // 替换源时间格式
	}

	// 日志输出目标, 可以是控制台, 文件或两者的组合
	writer, err := getLogOutput()
	if err != nil {
		slog.Error("初始化日志记录器失败", slog.String("error", err.Error()))
		return err
	}

	if Config.LOG.JSON_FORMAT {
		logger = slog.New(slog.NewJSONHandler(writer, &opts))
	} else {
		logger = slog.New(slog.NewTextHandler(writer, &opts))
	}

	slog.Info("日志记录器初始化成功")
	// 添加默认日志字段
	// logger = logger.With("module", "APP")
	// 设置为默认日志
	slog.SetDefault(logger)

	// 日志切割
	if Config.LOG.ROTATE_DAILY && rfh != nil {
		RotateDaily()
	}

	return nil
}

// 获取输出日志的目标
func getLogOutput() (io.Writer, error) {
	fileName := "app-" + utils.GetIpAddress() + ".log"
	logFilePath := path.Join(Config.LOG.PATH, fileName)

	// 设置日志输出
	switch strings.ToUpper(Config.LOG.OUTPUT) {
	// 控制台
	case "CONSOLE":
		return os.Stdout, nil
	// 文件
	case "FILE":
		file, err := openLogFile(logFilePath)
		if err != nil {
			return nil, err
		}
		rfh = &rotatingFileHandler{
			filename: logFilePath,
			file:     file,
		}
		return rfh, nil
	// 控制台和文件
	case "BOTH":
		file, err := openLogFile(logFilePath)
		if err != nil {
			return nil, err
		}
		rfh = &rotatingFileHandler{
			filename: logFilePath,
			file:     file,
		}
		return io.MultiWriter(os.Stdout, rfh), nil
	// 默认控制台
	default:
		return os.Stdout, nil
	}
}

// 根据配置获取日志级别
func getLogLevel(level string) slog.Level {
	switch strings.ToUpper(level) {
	case "DEBUG":
		return slog.LevelDebug
	case "INFO":
		return slog.LevelInfo
	case "WARN":
		return slog.LevelWarn
	case "ERROR":
		return slog.LevelError
	default:
		return slog.LevelInfo
	}
}

// 根据配置获取时间格式
func getTimeFormatter() func([]string, slog.Attr) slog.Attr {
	timeFormatFunc := func(t time.Time) string {
		switch strings.ToUpper(Config.LOG.TIME_FORMAT) {
		case "HUMAN_READABLE_TIME":
			return t.Format("2006-01-02 15:04:05")
		case "UNIX_TIMESTAMP":
			return fmt.Sprint(t.Unix())
		case "ISO8601_TIME":
			return t.Format("2006-01-02T15:04:05.999999-07:00")
		default:
			return t.Format("2006-01-02 15:04:05")
		}
	}

	return func(groups []string, a slog.Attr) slog.Attr {
		if a.Key == slog.TimeKey {
			t := a.Value.Time()
			return slog.String(slog.TimeKey, timeFormatFunc(t))
		}
		return a
	}
}

// 函数传入的文件名, 可能是一个路径(示例: /log/log), 也可能是一个文件名(log)
func openLogFile(filename string) (*os.File, error) {
	// 获取文件路径
	dir := filepath.Dir(filename)
	// 检查文件夹是否存在
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		// 创建文件夹
		err := os.MkdirAll(dir, 0o755)
		if err != nil {
			slog.Error("创建日志目录失败", slog.String("error", err.Error()))
			return nil, err
		}
	}

	// 打开或创建文件
	f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
	if err != nil {
		slog.Error("打开日志文件失败", slog.String("error", err.Error()))
		return nil, err
	}

	return f, nil
}

// 轮询日志文件
// 实现了io.Writer接口, 用于写入日志到文件
type rotatingFileHandler struct {
	filename string
	file     *os.File
	mu       sync.Mutex
}

// 实现了io.Writer接口的Write方法
func (rfh *rotatingFileHandler) Write(p []byte) (n int, err error) {
	// rfh.mu.Lock()
	// defer rfh.mu.Unlock()
	return rfh.file.Write(p)
}

// 重命名日志文件
func (rfh *rotatingFileHandler) Rotate() error {
	rfh.mu.Lock()
	defer rfh.mu.Unlock()

	err := rfh.file.Close()
	if err != nil {
		return err
	}

	err = os.Rename(rfh.filename, rfh.filename+"."+time.Now().Format("2006-01-02T15-04-05")+".log")
	if err != nil {
		return err
	}

	rfh.file, err = openLogFile(rfh.filename)
	if err != nil {
		return err
	}
	return nil
}

// 日志切割
func RotateDaily() {
	once.Do(func() {
		go func() {
			for {
				now := time.Now()
				nextDay := now.AddDate(0, 0, 1)
				midnight := time.Date(nextDay.Year(), nextDay.Month(), nextDay.Day(), 0, 0, 0, 0, nextDay.Location())
				duration := midnight.Sub(now)

				// * 测试日志切割
				// now := time.Now()
				// nextMinute := now.Add(1 * time.Minute)
				// midnight := time.Date(nextMinute.Year(), nextMinute.Month(), nextMinute.Day(), nextMinute.Hour(), nextMinute.Minute(), 0, 0, nextMinute.Location())
				// duration := midnight.Sub(now)

				slog.Info("日志轮换信息", slog.String("next_rotate", midnight.String()))
				<-time.After(duration)
				slog.Info("开始日志文件轮换...")
				err := rfh.Rotate()
				if err != nil {
					slog.Error("日志文件轮换失败", slog.String("error", err.Error()))
				} else {
					slog.Info("日志文件轮换成功")
				}
			}
		}()
	})
}
